Skip to content

fix: vuln correlation fixes - #2582

Merged
rh-jfuller merged 7 commits into
guacsec:mainfrom
rh-jfuller:tc-5630
Aug 20, 2026
Merged

fix: vuln correlation fixes#2582
rh-jfuller merged 7 commits into
guacsec:mainfrom
rh-jfuller:tc-5630

Conversation

@rh-jfuller

@rh-jfuller rh-jfuller commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Contains various vuln correlation fixes which all have to be done together as there are multiple codepaths influencing each other.

Vulnerability correlation has at least 5 distinct query paths that should produce consistent results:

  • batch_severity_counts_sql() — SBOM list counting (raw SQL CTEs)
  • cpe_advisory_info_sql() — SBOM detail CPE matching (raw SQL)
  • product_advisory_info_sql() — SBOM detail product matching (raw SQL)
  • purl_status query in PurlDetails::from_entity — SeaORM
  • get_product_statuses_for_purl — SeaORM
  • Vulnerability detail cpe_status query — raw SQL

Plus the associated vuln analysis with each has its own filter logic (version matching, CPE context, deprecation), implemented independently.

We will address above multiple codepaths in future PRs, this set of fixes maintains status quo.

include CPE-only SBOM nodes in advisory detail and vulnerability backlink endpoints

The affected-vulnerability count for an SBOM is inconsistent across endpoints. A vuln matching an SBOM only through a package-level CPE (on a component with no PURL) is counted by the list query but
silently dropped by both detail endpoints:

The discrepancy appears when an SBOM contains a purl-less CPE component whose CPE matches vulnerabilities via cpe_status.

Both detail endpoints required matched SBOM nodes to have a
qualified_purl_id:

  1. cpe_advisory_info_sql() had an explicit
    WHERE p.qualified_purl_id IS NOT NULL filter.
  2. Vulnerability detail query used INNER JOIN sbom_node_purl_ref
    and INNER JOIN qualified_purl, eliminating CPE-only nodes.
  3. IdSet/QueryCatcher/SbomStatusCatcher all declared
    qualified_purl as mandatory, so even if the SQL returned NULLs the
    deserialization would fail.

fwiw - list endpoint's counting CTE had no such requirement, producing correct (higher) count.

Fix

  • Remove IS NOT NULL filter from cpe_advisory_info_sql()
  • Change INNER JOINLEFT JOIN for sbom_node_purl_ref and
    qualified_purl in the vulnerability detail cpe_status query
  • Make qualified_purl optional in IdSet, QueryCatcher, and
    SbomStatusCatcher
  • Emit empty purl array for CPE-only packages instead of skipping
    entire row

which fixes TC-5630

add version_matches filter to product status query for PURL analysis

fixes TC-5170

add CPE context filter to product status query for PURL analysis

fixes TC-5171

Summary by Sourcery

Include CPE-only SBOM components in vulnerability and advisory detail results.

Bug Fixes:

  • Include SBOM components matched only through CPEs in advisory and vulnerability detail responses, keeping affected-vulnerability counts consistent across endpoints.

Enhancements:

  • Allow missing qualified PURLs in SBOM detail models and return empty PURL arrays for CPE-only components.
  • Apply version matching when retrieving product statuses for a specific PURL.

Summary by Sourcery

Fix vulnerability correlation results for CPE-only SBOM components and context-aware PURL product statuses.

Bug Fixes:

  • Include SBOM components matched only through CPEs in advisory and vulnerability detail responses, keeping affected-vulnerability results consistent across endpoints.
  • Apply CPE context filtering when retrieving product statuses for a PURL.

Enhancements:

  • Allow missing qualified PURLs in SBOM detail models and return empty PURL lists for CPE-only components.
  • Centralize construction of CPE context filters used by PURL status queries.

@sourcery-ai

sourcery-ai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR ensures CPE-only SBOM components (nodes with no PURL but with CPE that matches vulnerabilities) are correctly surfaced in advisory and vulnerability detail endpoints instead of being silently dropped, by making qualified_purl optional across the Rust models and SQL, relaxing joins/filters, and emitting empty purl arrays when appropriate.

File-Level Changes

Change Details Files
Make qualified_purl optional in advisory-related Rust models and query catcher flow so CPE-only SBOM nodes are preserved instead of discarded.
  • Change IdSet.qualified_purl_id from Uuid to Option and adjust FromQueryResult to tolerate NULL values.
  • Guard insertion into qualified_purl_ids_set so only Some IDs are collected.
  • Update lookup of qualified_purl in SbomDetails to handle optional IDs and propagate NotFound errors only when a non-NULL ID is missing.
  • Change QueryCatcher.qualified_purl to Option<Arc<qualified_purl::Model>> and adjust FromQueryResult to use an optional multi-model extractor.
  • Update SbomAdvisory construction to build the purl vector conditionally, emitting an empty array when qualified_purl is None.
modules/fundamental/src/sbom/model/details.rs
modules/fundamental/src/sbom/service/sbom.rs
Relax SQL constraints and join patterns in CPE advisory and vulnerability detail queries so SBOM nodes without qualified_purl_id are included.
  • Update cpe_advisory_info_sql documentation comment to describe inclusion of nodes without qualified_purl_id and their relationship to CPE-only components.
  • Remove the p.qualified_purl_id IS NOT NULL filter from cpe_advisory_info_sql and rely solely on version_matches for filtering.
  • Adjust cpe_status_query SELECT to source sbom_id and node_id from sbom_node_cpe_ref instead of sbom_node_purl_ref.
  • Change sbom_node_purl_ref and qualified_purl joins in the vulnerability advisory CPE status query from INNER JOIN to LEFT JOIN, making the purl association optional.
modules/fundamental/src/sbom/model/raw_sql.rs
modules/fundamental/src/vulnerability/model/details/vulnerability_advisory.rs
Allow vulnerability SBOM status building to handle optional qualified_purl and avoid emitting purl status for CPE-only entries.
  • Change SbomStatusCatcher.qualified_purl to Option<qualified_purl::Model> and use an optional multi-model extractor in FromQueryResult.
  • Update VulnerabilitySbomStatus aggregation to insert PurlSummary into purl_status only when a qualified_purl is present.
modules/fundamental/src/vulnerability/model/details/vulnerability_advisory.rs

Possibly linked issues

  • #GET /api/v2/sbom/{id}/advisory is missing vulnerabilities/advisories: PR adjusts advisory/vulnerability detail queries to include CPE-only SBOM nodes, fixing missing advisories in SBOM advisory endpoint.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@rh-jfuller
rh-jfuller requested a review from a team August 17, 2026 09:02

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="modules/fundamental/src/sbom/model/details.rs" line_range="53" />
<code_context>
         Ok(Self {
             advisory_id: res.try_get("", "advisory_id")?,
-            qualified_purl_id: res.try_get("", "qualified_purl_id")?,
+            qualified_purl_id: res.try_get("", "qualified_purl_id").ok(),
             sbom_id: res.try_get("", "sbom_id")?,
             sbom_node_id: res.try_get("", "node_id")?,
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `.ok()` here converts all DB errors (not just NULLs) into `None`, potentially hiding genuine query/schema issues.

If you only want to map NULLs to `None`, consider using `try_get::<Option<Uuid>>` (if available) or explicitly handling `DbErr::RecordNotFound`/NULL. The current `.ok()` call turns any failure to read `qualified_purl_id` (including type/column errors) into `None`, which can hide real bugs and lead to inconsistent data.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread modules/fundamental/src/sbom/model/details.rs Outdated
@PhilipCattanach

Copy link
Copy Markdown

@rh-jfuller Shouldn't this PR include some tests to prove the list and detail endpoints produce consistent results for CPE only and CPE + PURL components?

… backlink endpoints

SBOM list endpoint correctly counts vulnerabilities matched via
package level CPE identity (cpe_status) on nodes without a PURL but
both detail endpoints silently dropped them:

- /sbom/{id}/advisory: cpe_advisory_info_sql() filtered out rows /w
  NULL qualified_purl_id and IdSet required it to be non-optional Uuid
- /vulnerability/{id}: cpe_status sub-query INNER JOINed
  sbom_node_purl_ref and qualified_purl eliminating CPE-only nodes

This causes list page to show higher vuln count detail pages
…ysis

get_product_statuses_for_purl joins version_range but never applies
version_matches(), returning all version ranges for package name
regardless of whether queried version falls within range or
uses a compatible version scheme which causes false-positive vuln match

This commit passes pURL version into get_product_statuses_for_purl and add
version_matches(purl_version, version_range.*) filter, matching
pattern already used by sibling purl_status query
@rh-jfuller rh-jfuller changed the title fix: include CPE-only SBOM nodes in advisory and vulnerability detail endpoints fix: vuln correlation fixes Aug 17, 2026
get_product_statuses_for_purl returns product statuses without checking
whether the advisory's CPE context applies to the SBOMs containing the
queried PURL.

Add the same three-way CPE context filter already used by the sibling
purl_status query: allow when context_cpe_id is NULL, when it matches
the SBOM's describing CPEs, or when the SBOM has no describing CPEs.
@rh-jfuller

Copy link
Copy Markdown
Contributor Author

@SourceryAI summary

@rh-jfuller

Copy link
Copy Markdown
Contributor Author

@SourceryAI guide

@ptomanRH
ptomanRH requested a review from mrrajan August 18, 2026 09:12
Comment thread modules/fundamental/src/purl/model/details/purl.rs Outdated
Comment thread modules/fundamental/src/purl/model/details/purl.rs

@mrrajan mrrajan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Add three tests exercising CPE-context and purl-less CPE node handling in
SBOM advisory correlation, with CycloneDX fixtures under
etc/test-data/cyclonedx/TC-5630.

- sbom_details_purlless_cpe_node_consistency (passing): guards the fix for
  a vulnerability matched only via a package-level CPE on a component with a
  CPE but no PURL. Asserts the three views agree: /sbom/{id}/advisory lists
  it, the SBOM list severity counts match, and /vulnerability/{id} backlinks
  the SBOM. Uses the opentofu SBOM (purl-less os:hummingbird node) and a
  synthetic CVE-2026-12151 matching cpe:/a:redhat:hummingbird:1
- sbom_details_wrong_product_cpe_context (#[ignore])
- sbom_details_wrong_product_satellite (#[ignore])
The two reproducers are ignored so CI stays green; remove #[ignore] once
context scoping derives product identity from the OS/root component CPE.
@rh-jfuller
rh-jfuller enabled auto-merge August 20, 2026 10:59
@rh-jfuller
rh-jfuller added this pull request to the merge queue Aug 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 20, 2026
@rh-jfuller
rh-jfuller added this pull request to the merge queue Aug 20, 2026
Merged via the queue into guacsec:main with commit 610b122 Aug 20, 2026
14 of 15 checks passed
@rh-jfuller
rh-jfuller deleted the tc-5630 branch August 20, 2026 13:09
@github-project-automation github-project-automation Bot moved this to Done in Trustify Aug 20, 2026
@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.4.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.4.z
git worktree add -d .worktree/backport-2582-to-release/0.4.z origin/release/0.4.z
cd .worktree/backport-2582-to-release/0.4.z
git switch --create backport-2582-to-release/0.4.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.5.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.5.z
git worktree add -d .worktree/backport-2582-to-release/0.5.z origin/release/0.5.z
cd .worktree/backport-2582-to-release/0.5.z
git switch --create backport-2582-to-release/0.5.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.4.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.4.z
git worktree add -d .worktree/backport-2582-to-release/0.4.z origin/release/0.4.z
cd .worktree/backport-2582-to-release/0.4.z
git switch --create backport-2582-to-release/0.4.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.6.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.6.z
git worktree add -d .worktree/backport-2582-to-release/0.6.z origin/release/0.6.z
cd .worktree/backport-2582-to-release/0.6.z
git switch --create backport-2582-to-release/0.6.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.5.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.5.z
git worktree add -d .worktree/backport-2582-to-release/0.5.z origin/release/0.5.z
cd .worktree/backport-2582-to-release/0.5.z
git switch --create backport-2582-to-release/0.5.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.6.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.6.z
git worktree add -d .worktree/backport-2582-to-release/0.6.z origin/release/0.6.z
cd .worktree/backport-2582-to-release/0.6.z
git switch --create backport-2582-to-release/0.6.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.4.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.4.z
git worktree add -d .worktree/backport-2582-to-release/0.4.z origin/release/0.4.z
cd .worktree/backport-2582-to-release/0.4.z
git switch --create backport-2582-to-release/0.4.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.5.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.5.z
git worktree add -d .worktree/backport-2582-to-release/0.5.z origin/release/0.5.z
cd .worktree/backport-2582-to-release/0.5.z
git switch --create backport-2582-to-release/0.5.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

@trustify-ci-bot

Copy link
Copy Markdown

Backport failed for release/0.6.z, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin release/0.6.z
git worktree add -d .worktree/backport-2582-to-release/0.6.z origin/release/0.6.z
cd .worktree/backport-2582-to-release/0.6.z
git switch --create backport-2582-to-release/0.6.z
git cherry-pick -x bcf709a1aa94ed4d1204cdb6fa3e6b3cf3844e7d 0dcab72d80bc7266db5a61823619b69894caa6b0 b9eddf35bcee7335f6e58ba39a764f63f19789d3 21960720482cd1872e0e550b35eac358cde98a95 0c5ede8eb9a8253d0567ad35b7dda43bd5e50abb 02aa413dbeea925f778af1b3d9647d54384f3f3e 610b122b1a1049693eeb66ea3093d4c3cb132b09

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants